外部活动导入接口 (External Event Import)

接口概述

该接口用于从第三方活动平台导入活动数据到 Pear 系统。支持多个主流活动平台的导入,包括 Eventbrite、Poshvip、Raco、Shotgun 和 DiceFM。

[!important] 架构说明 本接口为 Admin Server 代理接口,实际业务逻辑由下游 Pear API 服务处理。Admin Server 负责请求转发和认证。


请求信息

基本参数

属性
请求地址 /users/tool/external-event-import/:userId
请求方式 POST
Content-Type application/json
认证方式 JWT Bearer Token

路径参数 (Path Parameters)

参数名 类型 必填 说明
userId string (UUID) 目标用户 ID,活动将导入到该用户账户下

请求头 (Headers)

Header 类型 必填 说明
Authorization string JWT 认证令牌,格式:Bearer <token>
Content-Type string 固定值:application/json
timezone string ⚠️ 用户时区,如 Asia/Shanghai。部分平台解析需要时区信息
x-track-id string (UUID) 请求追踪 ID,用于日志关联
accept string 接受的响应类型,如 application/json

[!warning] 时区重要性 timezone 头信息会被转发到下游服务。某些活动平台(如 Poshvip)的时间解析依赖此时区信息,缺失可能导致 Failed to fetch timeZone 错误。


请求体 (Request Body)

请求结构

interface EventImportRequest {
  type: EventImportType;  // 活动来源平台类型
  url: string;            // 活动页面 URL
}

字段说明

字段 类型 必填 说明
type enum 活动来源平台类型,见下方枚举值
url string 第三方平台的完整活动 URL

type 枚举值

平台 URL 示例
eventbrite Eventbrite https://www.eventbrite.com/e/xxx-tickets-123456789
poshvip Poshvip https://posh.vip/e/xxx
raco Raco -
shotgun Shotgun -
dicefm DiceFM -

请求示例

{
  "type": "eventbrite",
  "url": "https://www.eventbrite.com/e/the-association-cocktail-classes-tickets-164264039163"
}
{
  "type": "poshvip",
  "url": "https://posh.vip/e/example-event"
}

响应信息

成功响应

[!note] 响应说明 实际响应结构由下游 Pear API 服务决定,Admin Server 透传下游响应的 data 字段。

interface SuccessResponse {
  // 响应结构由下游服务定义
  // 通常包含导入的活动信息
}

成功响应示例

{
  "id": "event-uuid-here",
  "title": "The Association Cocktail Classes",
  "description": "Event description...",
  "startTime": "2026-03-15T19:00:00Z",
  "endTime": "2026-03-15T22:00:00Z",
  "location": {
    "name": "Venue Name",
    "address": "123 Street, City"
  },
  // ... 其他活动字段
}

错误响应

错误响应结构

interface ErrorResponse {
  code: number;      // HTTP 状态码
  message: string;   // 错误信息
  data?: any;        // 附加错误详情(可选)
}

常见错误码

HTTP 状态码 错误场景 说明
400 参数验证失败 type 不是有效的枚举值,或 url 格式不正确
401 认证失败 JWT Token 无效或已过期
404 用户不存在 指定的 userId 在系统中不存在
409 业务冲突 活动已存在或其他业务冲突
500 服务器内部错误 下游服务异常、网络超时等

错误响应示例

参数验证失败 (400)

{
  "code": 400,
  "message": "type must be one of the following values: eventbrite, poshvip, raco, shotgun, dicefm",
  "data": null
}

时区获取失败 (500)

{
  "code": 500,
  "message": "Failed to fetch timeZone",
  "data": {
    "platform": "poshvip",
    "url": "https://posh.vip/e/xxx"
  }
}

活动 URL 无效

{
  "code": 400,
  "message": "Invalid event URL format",
  "data": {
    "url": "invalid-url"
  }
}

完整请求示例

cURL 示例

curl -X POST 'https://admin.katana-api.1m.app/users/tool/external-event-import/ad57de31-bf92-413b-ae4d-8609b5ff0680' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' \
  -H 'timezone: Asia/Shanghai' \
  -H 'x-track-id: ac025c47-a2de-4634-8067-8642e14a24c4' \
  -d '{
    "type": "eventbrite",
    "url": "https://www.eventbrite.com/e/the-association-cocktail-classes-tickets-164264039163"
  }'

JavaScript (fetch) 示例

const response = await fetch(
  'https://admin.katana-api.1m.app/users/tool/external-event-import/ad57de31-bf92-413b-ae4d-8609b5ff0680',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`,
      'timezone': 'Asia/Shanghai',
      'x-track-id': crypto.randomUUID()
    },
    body: JSON.stringify({
      type: 'eventbrite',
      url: 'https://www.eventbrite.com/e/the-association-cocktail-classes-tickets-164264039163'
    })
  }
);

const result = await response.json();

下游服务请求 (Pear API)

Admin Server 通过 RPC 调用下游 Pear API 服务,以下是转发请求的详细参数。

[!tip] 内部服务调用 此部分为 Admin Server 与 Pear API 之间的内部通信,供后端开发参考。

请求信息

属性
Base URL {KATANA_URL} (环境变量配置)
请求路径 /external-event-import/admin/:userId
请求方式 POST
Content-Type application/json

请求头 (Headers)

Admin Server 会自动添加以下认证头:

Header 来源 说明
Pear-Client-Id 环境变量 PEAR_CLIENT_ID 服务间认证标识
Pear-Client-Secret 环境变量 PEAR_CLIENT_SECRET 服务间认证密钥

[!note] 头信息传递 客户端传递的 timezonex-track-id 等头信息也会被转发到下游服务。

请求体 (Body)

请求体直接透传,与客户端请求体一致:

{
  "type": "eventbrite",
  "url": "https://www.eventbrite.com/e/xxx-tickets-123456789"
}

完整请求示例

# Admin Server -> Pear API 内部调用
curl -X POST '${KATANA_URL}/external-event-import/admin/ad57de31-bf92-413b-ae4d-8609b5ff0680' \
  -H 'Content-Type: application/json' \
  -H 'Pear-Client-Id: ${PEAR_CLIENT_ID}' \
  -H 'Pear-Client-Secret: ${PEAR_CLIENT_SECRET}' \
  -H 'timezone: Asia/Shanghai' \
  -d '{
    "type": "eventbrite",
    "url": "https://www.eventbrite.com/e/the-association-cocktail-classes-tickets-164264039163"
  }'

响应处理

下游 Pear API 返回格式:

interface PearApiResponse<T> {
  data: T;  // Admin Server 提取此字段返回给客户端
}

Admin Server 提取 response.data.data 作为最终响应返回给客户端。

RPC Service 实现

// src/rpc/rpc.service.ts

async requestToPear<T = any>(
  url: string,
  config?: Parameters<typeof axios>[1],
): Promise<T> {
  try {
    const result = await axios(url, {
      baseURL: this.configService.get<string>('KATANA_URL'),
      ...config,
      headers: {
        ['Pear-Client-Id']: this.configService.get('PEAR_CLIENT_ID'),
        ['Pear-Client-Secret']: this.configService.get('PEAR_CLIENT_SECRET'),
        ...config?.headers,  // 合并额外的 headers
      },
    });
    return result.data.data as T;  // 提取 data.data 返回
  } catch (error: any) {
    throw new CustomException(
      error?.response?.status ?? 409,
      error?.response?.data?.message,
      error?.response?.data?.data,
    );
  }
}

业务逻辑

处理流程

    participant Client as 客户端
    participant Admin as Admin Server
    participant Pear as Pear API (下游服务)
    participant Platform as 第三方平台

    Client->>Admin: POST /external-event-import/:userId
    Note over Client,Admin: Headers: Authorization, timezone
    Note over Client,Admin: Body: {type, url}

    Admin->>Admin: JWT 认证验证
    Admin->>Admin: 参数验证 (class-validator)

    Admin->>Pear: POST /external-event-import/admin/:userId
    Note over Admin,Pear: 转发请求体和 Headers

    Pear->>Platform: 爬取/解析活动数据
    Platform-->>Pear: 返回活动信息

    Pear->>Pear: 创建活动记录
    Pear-->>Admin: 返回活动数据

    Admin-->>Client: 透传响应数据

架构说明

[!info] 代理模式 Admin Server 采用代理模式,核心职责包括:

  1. 认证鉴权:验证 JWT Token 有效性
  2. 参数校验:使用 class-validator 验证请求参数
  3. 请求转发:通过 RPC 调用下游 Pear API 服务
  4. 错误处理:捕获下游异常并转换为统一错误格式

关键代码位置

组件 文件路径 说明
Controller src/users/users.tool.controller.ts:85-97 端点定义和请求处理
DTO src/users/user.interface.ts:190-196 EventImportRequest 类型定义
Enum src/users/user.interface.ts:182-188 EventImportType 枚举定义
RPC Service src/rpc/rpc.service.ts 下游服务调用
Exception Filter src/filter/any.exception.filter.ts 全局异常处理

注意事项

必读事项

[!danger] 认证要求

  • 所有请求必须携带有效的 JWT Bearer Token
  • Token 通过 Authorization 头传递
  • 未认证请求将返回 401 Unauthorized

[!warning] 时区设置

  • 强烈建议始终传递 timezone
  • 格式为 IANA 时区标识符,如 Asia/ShanghaiAmerica/New_York
  • 缺失可能导致部分平台导入失败

[!caution] URL 有效性

  • 确保 URL 为目标平台的有效活动页面
  • URL 必须可公开访问(无需登录即可查看)
  • 私有或已下线的活动可能无法导入

平台特定说明

平台 特殊要求 已知限制
eventbrite URL 必须包含 /e/ 路径 -
poshvip 必须传递 timezone 头 可能出现 timeZone 获取失败
raco - -
shotgun - -
dicefm - -

错误排查

错误信息 可能原因 解决方案
Failed to fetch timeZone Poshvip 平台缺少 timezone 头 添加 timezone: Asia/Shanghai
type must be one of... type 参数值无效 检查 type 是否为支持的枚举值
Requested resource not found 用户 ID 不存在 验证 userId 是否正确
Network timeout 下游服务响应超时 重试请求或检查服务状态

相关接口

  • [[kat-music-widget-import-音乐组件导入]] - 音乐组件导入接口
  • [[kat-external-user-import-外部用户导入]] - Partner/Promoter 用户导入
  • [[kat-web-scraper-网页爬虫]] - Komi/Linktr 爬虫接口

变更历史

日期 版本 变更内容
2026-03-03 1.0 初始版本,梳理完整接口文档

附录

EventImportRequest 类型定义

// src/users/user.interface.ts

export enum EventImportType {
  EVENTBRITE = 'eventbrite',
  POSHVIP = 'poshvip',
  RACO = 'raco',
  SHOTGUN = 'shotgun',
  DICEFM = 'dicefm',
}

export class EventImportRequest {
  @IsEnum(EventImportType)
  type: EventImportType;

  @IsString()
  url: string;
}

Controller 实现

// src/users/users.tool.controller.ts

@Post('external-event-import/:userId')
async importEvent(
  @Param('userId') userId: string,
  @Body() body: EventImportRequest,
) {
  return this.rpcService.requestToPear(
    `external-event-import/admin/${userId}`,
    {
      method: 'POST',
      data: body,
    },
  );
}

results matching ""

    No results matching ""